/*
 * Copyright 2005 Shinya Kasatani
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var locatorsSort = null;

function LocatorBuilders(window) {
    this.window = window;
    //this.log = new Log("LocatorBuilders");
}

LocatorBuilders.prototype.detach = function() {
    if (this.window._locator_pageBot) {
        //this.log.debug(this.window);
        this.window._locator_pageBot = undefined;
        // Firefox 3 (beta 5) throws "Security Manager vetoed action" when we use delete operator like this:
        // delete this.window._locator_pageBot;
    }
};

LocatorBuilders.prototype.pageBot = function() {
    var pageBot = this.window._locator_pageBot;
    if (pageBot == null) {
        //pageBot = BrowserBot.createForWindow(this.window);
        pageBot = new MozillaBrowserBot(this.window);
        var self = this;
        pageBot.getCurrentWindow = function() {
            return self.window;
        };
        this.window._locator_pageBot = pageBot;
    }
    return pageBot;
};

LocatorBuilders.prototype.buildWith = function(name, e, opt_contextNode) {
    return LocatorBuilders.builderMap[name].call(this, e, opt_contextNode);
};

LocatorBuilders.prototype.elementEquals = function(name, e, locator) {
    var fe = this.findElement(locator);
    //TODO: add match function to the ui locator builder, note the inverted parameters
    return (e == fe) || (LocatorBuilders.builderMap[name] && LocatorBuilders.builderMap[name].match && LocatorBuilders.builderMap[name].match(e, fe));
};

LocatorBuilders.prototype.build = function(e) {
    var locators = this.buildAll(e);
    if (locators.length > 0) {
        return locators[0][0];
    } else {
        return "LOCATOR_DETECTION_FAILED";
    }
};

LocatorBuilders.prototype.buildAll = function(el) {
    var e = core.firefox.unwrap(el); //Samit: Fix: Do the magic to get it to work in Firefox 4
    var xpathLevel = 0;
    var maxLevel = 10;
    var buildWithResults;
    var locators = [];
    //this.log.debug("getLocator for element " + e);
    var coreLocatorStrategies = this.pageBot().locationStrategies;
    for (var i = 0; i < LocatorBuilders.order.length; i++) {
        var finderName = LocatorBuilders.order[i];
        var locator;
        var locatorResults = []; // Array to hold buildWith results
        //this.log.debug("trying " + finderName);
        try {
            buildWithResults = this.buildWith(finderName, e);

            //only implement if no native implementation is available
            if (!Array.isArray) {
                Array.isArray = function(obj) {
                    return Object.prototype.toString.call(obj) === '[object Array]';
                }
            };

            // If locator is an array then dump its element in a new array
            if (Array.isArray(buildWithResults)) {
                for (var j = 0; j < buildWithResults.length; j++) {
                    locatorResults.push(buildWithResults[j]);
                }
            } else {
                locatorResults.push(buildWithResults);
            }

            for (var j = 0; j < locatorResults.length; j++) {
                locator = locatorResults[j];

                if (locator) {
                    locator = String(locator);
                    if (finderName != 'tac') {
                        var fe = this.findElement(locator);
                        if ((e == fe) || (coreLocatorStrategies[finderName] && coreLocatorStrategies[finderName].is_fuzzy_match &&
                                coreLocatorStrategies[finderName].is_fuzzy_match(fe, e))) {
                            locators.push([locator, finderName]);
                        }
                    } else {
                        locators.splice(0, 0, [locator, finderName]);
                    }
                }
            }
        } catch (e) {
            // TODO ignore the buggy locator builder for now
            //this.log.debug("locator exception: " + e);
        }
    }
    if (locatorsSort) {
        locators = this.processLocators(locators);
    }
    return locators;
};

LocatorBuilders.prototype.getPrevText = function(el) {
    var e = core.firefox.unwrap(el); //Samit: Fix: Do the magic to get it to work in Firefox 4
    var buildWithResults;
    //this.log.debug("getLocator for element " + e);
    var coreLocatorStrategies = this.pageBot().locationStrategies;
    var textTemp = "";
    for (var i = 0; i < LocatorBuilders.order.length; i++) {
        var finderName = LocatorBuilders.order[i];
        var locator;
        var locatorResults = []; // Array to hold buildWith results
        //this.log.debug("trying " + finderName);
        try {
            buildWithResults = this.buildWith(finderName, e);

            //only implement if no native implementation is available
            if (!Array.isArray) {
                Array.isArray = function(obj) {
                    return Object.prototype.toString.call(obj) === '[object Array]';
                }
            };
            // If locator is an array then dump its element in a new array
            if (Array.isArray(buildWithResults)) {
                for (var j = 0; j < buildWithResults.length; j++) {
                    locatorResults.push(buildWithResults[j]);
                }
            } else {
                locatorResults.push(buildWithResults);
            }
            for (var j = 0; j < locatorResults.length; j++) {
                locator = locatorResults[j];
                if (locator) {
                    locator = String(locator);

                    if (finderName != 'tac') {
                        var fe = this.findElement(locator);
                        if (fe != null) {
                            textTemp = this.getParentText(fe);
                        }
                        if (textTemp != "") {
                            break;
                        }
                    }
                }
            }
            if (textTemp != "") {
                break;
            }
        } catch (e) {}
    }
    return textTemp;
};

LocatorBuilders.prototype.findElement = function(locator) {
    try {
        return this.pageBot().findElement(locator);
    } catch (error) {
        //this.log.debug("findElement failed: " + error + ", locator=" + locator);
        return null;
    }
};
LocatorBuilders.prototype.findElementsByXpath = function(locator) {
    try {
        return this.pageBot().locateElementsByXPath(locator, this.window.document);
    } catch (error) {
        //this.log.debug("findElement failed: " + error + ", locator=" + locator);
        return [];
    }
};

LocatorBuilders.prototype.getParentText = function(ele) {
    try {
        //先往上找10层看有没有label,有的话就用label的内容
        var eleLabelTmp = ele;
        var tempeleClientRect = ele.getBoundingClientRect();
        for (var i = 0; i <= 10; i++) {
            eleLabelTmp = eleLabelTmp.parentNode;
            if (eleLabelTmp != null) {
                var hasLabel = eleLabelTmp.getElementsByTagName('label');
                //有label
                if (hasLabel.length > 0) {
                    //获取第一个label,判断label顶部边框在元素底部边框之上,label的左边框在元素右侧边框之左,其他位置的label判定为不符合要求,直接退出循环
                    var tempClientRect = hasLabel[0].getBoundingClientRect();
                    if (tempeleClientRect.bottom > tempClientRect.top && tempeleClientRect.right > tempClientRect.left) {
                        var temp = this.processText(hasLabel[0].textContent);
                        if (temp != "") {
                            return temp.substring(0, 10);
                        }
                    } else {
                        break
                    }
                }
            } else {
                break;
            }
        }
        //没找到label,则正常记录内容
        var addtiontmp = "";
        var temp1 = ele.placeholder;
        if (temp1 != undefined && temp1 != "") {
            addtiontmp = temp1.substring(0, 10);
        }
        var eleNew = ele;
        for (var i = 0; i <= 10; i++) {
            eleNew = eleNew.parentNode;
            if (eleNew != null) {
                var temp = this.processText(eleNew.textContent);
                if (temp != "") {
                    return addtiontmp + " " + temp.substring(0, 10);
                }
            } else {
                break;
            }
        }
    } catch (error) {
        return "";
    }
    return "";
};

LocatorBuilders.prototype.processText = function(txt) {
    return txt.replace(/[\t]/g, "").replace(/[\r\n]/g, "").replace(/(^\s*)|(\s*$)/g, "").replace("无匹配数据", "").replace("加载中", "");
};

/**
 * 处理返回的定位,将最精确的定位方式放在第一位
 */
LocatorBuilders.prototype.processLocators = function(locatorsTmp) {
    var lengthTmp = locatorsTmp.length;
    var locatorsTmpNew = new Array(locatorsTmp.length);
    var exctCount = 0;
    var notexctCount = 0;
    //循环判断是否准确,准确放首位,不准确放末位
    for (var i = 0; i < lengthTmp; i++) {
        //定位准确,放在首位,并且记录准确个数
        if (this.isLocatorExct(locatorsTmp[i])) {
            locatorsTmpNew[exctCount] = locatorsTmp[i];
            exctCount += 1;
        } else {
            //定位不准确,放在末位
            locatorsTmpNew[lengthTmp - notexctCount - 1] = locatorsTmp[i];
            notexctCount += 1;
        }
    }
    if (exctCount != 0) {
        //有精确的定位方式,继续将包含中文的和最短的放在第一位
        locatorsTmpNew = this.isLocatorExctAddition(locatorsTmpNew, exctCount)
    } else {
        //都不准确,返回默认的
        return locatorsTmp;
    }
    //console.log("locatorBuilders " + locatorsTmp);
    return locatorsTmpNew;
};

/**
 * 判断定位是否准确,准确返回true,不准确返回false
 */
LocatorBuilders.prototype.isLocatorExct = function(locatorTmp) {
    //xpath开头和document开头,如果包含中文,判定为准确(可能仍有下标),在判断有没有使用下标,有的话说明不准确.如果长度超过100,也认为不准确,太长了
    if (locatorTmp[0].indexOf("//") == 0 || locatorTmp[0].indexOf("xpath") == 0 || locatorTmp[0].indexOf("document") == 0) {
        if (locatorTmp[0].search(/[\u4e00-\u9fa5]+/) != -1) {
            return true;
        }
        if (locatorTmp[0].search(/\d{2,}/i) != -1) {
            return false;
        }
        if (locatorTmp[0].search(/\[\d+\]/i) != -1) {
            return false;
        } else if (locatorTmp[0].length > 100) {
            return false;
        } else {
            return true;
        }
    }
    //link开头,认为准确,返回true
    if (locatorTmp[0].indexOf("link") == 0) {
        return true;
    }
    //id开头,如果id包含两个或两个以上连续的数字认为不准确
    if (locatorTmp[0].indexOf("id") == 0) {
        if (locatorTmp[0].search(/\d{2,}/i) != -1) {
            return false;
        } else {
            return true;
        }
    }
    //name开头,如果包含两个或两个以上连续的数字认为不准确
    if (locatorTmp[0].indexOf("name") == 0) {
        if (locatorTmp[0].search(/\d{2,}/i) != -1) {
            return false;
        } else {
            return true;
        }
    }
    //css开头,认为不准确
    if (locatorTmp[0].indexOf("css") == 0) {
        return false;
    }
};
/**
 * 再次判断准确的定位方式长度,将有中文的放在前面,然后将最短的长度放在第一位,返回处理后的数组,需要传入原数组和准确的定位个数
 */
LocatorBuilders.prototype.isLocatorExctAddition = function(locatorsTmp, exctIndex) {
    var chineseIndex = 0;
    var exctchineseIndex = 0;
    //将包含中文的提前,包含中文提前
    for (var i = 0; i < exctIndex; i++) {
        if (locatorsTmp[i][0].search(/[\u4e00-\u9fa5]+/) != -1) {
            var tmp1 = locatorsTmp[i];
            locatorsTmp[chineseIndex] = locatorsTmp[i];
            locatorsTmp[i] = tmp1;
            chineseIndex += 1;
        }
    }
    //将中文最短的放在前面
    for (var i = 1; i < chineseIndex; i++) {
        var firstLength = locatorsTmp[0][0].length;
        var loopIndexLength = locatorsTmp[i][0].length;
        //首个元素比后面的长度长,并且不包含中文,调换两个元素位置
        if (firstLength > loopIndexLength) {
            var tmp = locatorsTmp[0];
            locatorsTmp[0] = locatorsTmp[i];
            locatorsTmp[i] = tmp;
        }
    }
    return locatorsTmp;
};

/*
 * Class methods
 */

LocatorBuilders.order = [];

LocatorBuilders.builderMap = {};
LocatorBuilders._preferredOrder = [];


// NOTE: for some reasons we does not use this part
// classObservable(LocatorBuilders);

LocatorBuilders.add = function(name, finder) {
    if (this.order.indexOf(name) < 0) {
        this.order.push(name);
    }
    this.builderMap[name] = finder;
    this._orderChanged();
};

/**
 * Call when the order or preferred order changes
 */
LocatorBuilders._orderChanged = function() {
    var changed = this._ensureAllPresent(this.order, this._preferredOrder);
    this._sortByRefOrder(this.order, this._preferredOrder);
    if (changed) {
        // NOTE: for some reasons we does not use this part
        // this.notify('preferredOrderChanged', this._preferredOrder);
    }
};

/**
 * Set the preferred order of the locator builders
 *
 * @param preferredOrder can be an array or a comma separated string of names
 */
LocatorBuilders.setPreferredOrder = function(preferredOrder) {
    if (typeof preferredOrder === 'string') {
        this._preferredOrder = preferredOrder.split(',');
    } else {
        this._preferredOrder = preferredOrder;
    }
    this._orderChanged();
};

/**
 * Returns the locator builders preferred order as an array
 */
LocatorBuilders.getPreferredOrder = function() {
    return this._preferredOrder;
};

/**
 * Sorts arrayToSort in the order of elements in sortOrderReference
 * @param arrayToSort
 * @param sortOrderReference
 */
LocatorBuilders._sortByRefOrder = function(arrayToSort, sortOrderReference) {
    var raLen = sortOrderReference.length;
    arrayToSort.sort(function(a, b) {
        var ai = sortOrderReference.indexOf(a);
        var bi = sortOrderReference.indexOf(b);
        return (ai > -1 ? ai : raLen) - (bi > -1 ? bi : raLen);
    });
};

/**
 * Function to add to the bottom of destArray elements from source array that do not exist in destArray
 * @param sourceArray
 * @param destArray
 */
LocatorBuilders._ensureAllPresent = function(sourceArray, destArray) {
    var changed = false;
    sourceArray.forEach(function(e) {
        if (destArray.indexOf(e) == -1) {
            destArray.push(e);
            changed = true;
        }
    });
    return changed;
};

/*
 * Utility function: Encode XPath attribute value.
 */
LocatorBuilders.prototype.attributeValue = function(value) {
    if (value.indexOf("'") < 0) {
        return "'" + value + "'";
    } else if (value.indexOf('"') < 0) {
        return '"' + value + '"';
    } else {
        var result = 'concat(';
        var part = "";
        while (true) {
            var apos = value.indexOf("'");
            var quot = value.indexOf('"');
            if (apos < 0) {
                result += "'" + value + "'";
                break;
            } else if (quot < 0) {
                result += '"' + value + '"';
                break;
            } else if (quot < apos) {
                part = value.substring(0, apos);
                result += "'" + part + "'";
                value = value.substring(part.length);
            } else {
                part = value.substring(0, quot);
                result += '"' + part + '"';
                value = value.substring(part.length);
            }
            result += ',';
        }
        result += ')';
        return result;
    }
};

LocatorBuilders.prototype.xpathHtmlElement = function(name) {
    if (this.window.document.contentType == 'application/xhtml+xml') {
        // "x:" prefix is required when testing XHTML pages
        return "x:" + name;
    } else {
        return name;
    }
};

LocatorBuilders.prototype.relativeXPathFromParent = function(current) {
    var index = this.getNodeNbr(current);
    var currentPath = '/' + this.xpathHtmlElement(current.nodeName.toLowerCase());
    if (index > 0) {
        currentPath += '[' + (index + 1) + ']';
    }
    return currentPath;
};

LocatorBuilders.prototype.getNodeNbr = function(current) {
    var childNodes = current.parentNode.childNodes;
    var total = 0;
    var index = -1;
    for (var i = 0; i < childNodes.length; i++) {
        var child = childNodes[i];
        if (child.nodeName == current.nodeName) {
            if (child == current) {
                index = total;
            }
            total++;
        }
    }
    return index;
};

LocatorBuilders.prototype.getCSSSubPath = function(e) {
    var css_attributes = ['id', 'name', 'class', 'type', 'alt', 'title', 'value'];
    for (var i = 0; i < css_attributes.length; i++) {
        var attr = css_attributes[i];
        var value = e.getAttribute(attr);
        if (value) {
            if (attr == 'id')
                return '#' + value;
            if (attr == 'class')
                return e.nodeName.toLowerCase() + '.' + value.replace(/\s+/g, ".").replace("..", ".");
            return e.nodeName.toLowerCase() + '[' + attr + '="' + value + '"]';
        }
    }
    if (this.getNodeNbr(e))
        return e.nodeName.toLowerCase() + ':nth-of-type(' + this.getNodeNbr(e) + ')';
    else
        return e.nodeName.toLowerCase();
};

LocatorBuilders.prototype.preciseXPath = function(xpath, e) {
    //only create more precise xpath if needed
    if (this.findElement(xpath) != e) {
        var result = e.ownerDocument.evaluate(xpath, e.ownerDocument, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        //skip first element (result:0 xpath index:1)
        for (var i = 0, len = result.snapshotLength; i < len; i++) {
            var newPath = 'xpath=(' + xpath + ')[' + (i + 1) + ']';
            if (this.findElement(newPath) == e) {
                return newPath;
            }
        }
    }
    return xpath;
}

/*
 * ===== builders =====
 */

LocatorBuilders.add('ui', function(pageElement) {
    return UIMap.getInstance().getUISpecifierString(pageElement,
        this.window.document);
});

LocatorBuilders.add('xpath:position', function(e, opt_contextNode) {
    //this.log.debug("positionXPath: e=" + e);
    var path = '';
    var current = e;
    while (current != null && current != opt_contextNode) {
        var currentPath;
        if (current.parentNode != null) {
            currentPath = this.relativeXPathFromParent(current);
        } else {
            currentPath = '/' + this.xpathHtmlElement(current.nodeName.toLowerCase());
        }
        path = currentPath + path;
        var locator = '/' + path;
        if (e == this.findElement(locator)) {
            return locator;
        }
        current = current.parentNode;
        //this.log.debug("positionXPath: current=" + current);
    }
    return null;
});

LocatorBuilders.add('id', function(e) {
    if (e.id) {
        return 'id=' + e.id;
    }
    return null;
});

LocatorBuilders.add('link', function(e) {
    if (e.nodeName == 'A') {
        var text = e.textContent;
        if (!text.match(/^\s*$/)) {
            return "link=" + exactMatchPattern(text.replace(/\xA0/g, " ").replace(/^\s*(.*?)\s*$/, "$1"));
        }
    }
    return null;
});

LocatorBuilders.add('name', function(e) {
    if (e.name) {
        return 'name=' + e.name;
    }
    return null;
});

/*
 * This function is called from DOM locatorBuilders
 */
LocatorBuilders.prototype.findDomFormLocator = function(form) {
    if (form.hasAttribute('name')) {
        var name = form.getAttribute('name');
        var locator = "document." + name;
        if (this.findElement(locator) == form) {
            return locator;
        }
        locator = "document.forms['" + name + "']";
        if (this.findElement(locator) == form) {
            return locator;
        }
    }
    var forms = this.window.document.forms;
    for (var i = 0; i < forms.length; i++) {
        if (form == forms[i]) {
            return "document.forms[" + i + "]";
        }
    }
    return null;
};

LocatorBuilders.add('dom:name', function(e) {
    if (e.form && e.name) {
        var formLocator = this.findDomFormLocator(e.form);
        if (formLocator) {
            var candidates = [formLocator + "." + e.name,
                formLocator + ".elements['" + e.name + "']"
            ];
            for (var c = 0; c < candidates.length; c++) {
                var locator = candidates[c];
                var found = this.findElement(locator);
                if (found) {
                    if (found == e) {
                        return locator;
                    } else if (found instanceof NodeList) {
                        // multiple elements with same name
                        for (var i = 0; i < found.length; i++) {
                            if (found[i] == e) {
                                return locator + "[" + i + "]";
                            }
                        }
                    }
                }
            }
        }
    }
    return null;
});

LocatorBuilders.add('xpath:link', function(e) {
    if (e.nodeName == 'A') {
        var text = e.textContent;
        if (!text.match(/^\s*$/)) {
            return this.preciseXPath("//" + this.xpathHtmlElement("a") + "[contains(text(),'" + text.replace(/^\s+/, '').replace(/\s+$/, '') + "')]", e);
        }
    }
    return null;
});

LocatorBuilders.add('xpath:img', function(e) {
    if (e.nodeName == 'IMG') {
        if (e.alt != '') {
            return this.preciseXPath("//" + this.xpathHtmlElement("img") + "[@alt=" + this.attributeValue(e.alt) + "]", e);
        } else if (e.title != '') {
            return this.preciseXPath("//" + this.xpathHtmlElement("img") + "[@title=" + this.attributeValue(e.title) + "]", e);
        } else if (e.src != '') {
            return this.preciseXPath("//" + this.xpathHtmlElement("img") + "[contains(@src," + this.attributeValue(e.src) + ")]", e);
        }
    }
    return null;
});

LocatorBuilders.add('xpath:attributes', function(e) {
    const PREFERRED_ATTRIBUTES = ['id', 'name', 'value', 'type', 'action', 'onclick'];
    var i = 0;

    function attributesXPath(name, attNames, attributes) {
        var locator = "//" + this.xpathHtmlElement(name) + "[";
        for (i = 0; i < attNames.length; i++) {
            if (i > 0) {
                locator += " and ";
            }
            var attName = attNames[i];
            locator += '@' + attName + "=" + this.attributeValue(attributes[attName]);
        }
        locator += "]";
        return this.preciseXPath(locator, e);
    }

    if (e.attributes) {
        var atts = e.attributes;
        var attsMap = {};
        for (i = 0; i < atts.length; i++) {
            var att = atts[i];
            attsMap[att.name] = att.value;
        }
        var names = [];
        // try preferred attributes
        for (i = 0; i < PREFERRED_ATTRIBUTES.length; i++) {
            var name = PREFERRED_ATTRIBUTES[i];
            if (attsMap[name] != null) {
                names.push(name);
                var locator = attributesXPath.call(this, e.nodeName.toLowerCase(), names, attsMap);
                if (e == this.findElement(locator)) {
                    return locator;
                }
            }
        }
    }
    return null;
});

LocatorBuilders.add('xpath:idRelative', function(e) {
    var path = '';
    var current = e;
    while (current != null) {
        if (current.parentNode != null) {
            path = this.relativeXPathFromParent(current) + path;
            if (1 == current.parentNode.nodeType && // ELEMENT_NODE
                current.parentNode.getAttribute("id")) {
                return this.preciseXPath("//" + this.xpathHtmlElement(current.parentNode.nodeName.toLowerCase()) +
                    "[@id=" + this.attributeValue(current.parentNode.getAttribute('id')) + "]" +
                    path, e);
            }
        } else {
            return null;
        }
        current = current.parentNode;
    }
    return null;
});

LocatorBuilders.add('xpath:href', function(e) {
    if (e.attributes && e.hasAttribute("href")) {
        href = e.getAttribute("href");
        if (href.search(/^http?:\/\//) >= 0) {
            return this.preciseXPath("//" + this.xpathHtmlElement("a") + "[@href=" + this.attributeValue(href) + "]", e);
        } else {
            // use contains(), because in IE getAttribute("href") will return absolute path
            return this.preciseXPath("//" + this.xpathHtmlElement("a") + "[contains(@href, " + this.attributeValue(href) + ")]", e);
        }
    }
    return null;
});

LocatorBuilders.add('dom:index', function(e) {
    if (e.form) {
        var formLocator = this.findDomFormLocator(e.form);
        if (formLocator) {
            var elements = e.form.elements;
            for (var i = 0; i < elements.length; i++) {
                if (elements[i] == e) {
                    return formLocator + ".elements[" + i + "]";
                }
            }
        }
    }
    return null;
});



LocatorBuilders.add('css', function(e) {
    var current = e;
    var sub_path = this.getCSSSubPath(e);
    while (this.findElement("css=" + sub_path) != e && current.nodeName.toLowerCase() != 'html') {
        sub_path = this.getCSSSubPath(current.parentNode) + ' > ' + sub_path;
        current = current.parentNode;
    }
    return "css=" + sub_path;
});

function trimString(strtmp) {
    return strtmp.replace(/\xA0/g, " ").replace(/(^\s*)|(\s*$)/g, "").replace("", "");
}
//自定义查询,最后一个元素用标签名,class属性和textContent定位
LocatorBuilders.add('xpath:class:text', function(e) {
    try {
        //如果当前元素没有文本值,则使用这种方式定位
        if (trimString(e.textContent) == "") {
            return null;
        }
        //先往上找10层,看看能不能获取到唯一定位方式
        var notcontains = '[not(contains(@style,"display: none"))]';
        var current = e;
        var endNode = "";
        var endNode1 = "";
        var endNode2 = "";
        for (var i = 0; i <= 5; i++) {
            if (i == 0) {
                //第一次循环判断自身
                if (current.className != "") {
                    endNode = current.localName + "[@class=\"" + current.className + "\"][contains(text(),\"" + trimString(current.textContent) + "\")]";
                    endNode1 = current.localName + notcontains + "[@class=\"" + current.className + "\"][contains(text(),\"" + trimString(current.textContent) + "\")]";
                    endNode2 = current.localName + notcontains + "[@class=\"" + current.className + "\"][contains(text(),\"" + trimString(current.textContent) + "\")]";
                } else {
                    endNode = current.localName + "[contains(text(),\"" + trimString(current.textContent) + "\")]";
                    endNode1 = current.localName + notcontains + "[contains(text(),\"" + trimString(current.textContent) + "\")]";
                    endNode2 = current.localName + notcontains + "[contains(text(),\"" + trimString(current.textContent) + "\")]";
                }
            } else {
                //后续循环往前面添加
                current = current.parentNode;
                if (current != null) {
                    var tempNode = endNode;
                    endNode = current.localName + "/" + endNode;
                    if (current.className != "") {
                        endNode1 = current.localName + "[@class=\"" + current.className + "\"]" + "/" + tempNode;
                        endNode2 = current.localName + notcontains + "[@class=\"" + current.className + "\"]" + "/" + tempNode;
                    } else {
                        endNode1 = current.localName + "/" + tempNode;
                        endNode2 = current.localName + notcontains + "/" + tempNode;
                    }
                } else {
                    //没有父类了,返回
                    return null;
                }
            }
            // 开始查询
            var tempElement = this.findElementsByXpath("//" + endNode);
            //如果不添加不显示的限制就只能查到一个,说明就用这一个就行
            if (tempElement.length == 1) {
                if (tempElement[0] == e) {
                    return "//" + endNode;
                } else {
                    return null;
                }
            } else if (tempElement.length == 0) {
                //如果查询不到说明元素没有这样的定位方式,不过应该不会出现这样的情况
                return null;
            } else {
                //如果查询到多个,则添加class属性再查询一次,如果添加显示限制后只查询到一个,说明就这样就行,否则继续往上找
                var tempElement1 = this.findElementsByXpath("//" + endNode1);
                if (tempElement1.length == 1) {
                    if (tempElement1[0] == e) {
                        return "//" + endNode1;
                    } else {
                        return null;
                    }
                }
                //如果查询到多个,则添加class属性和显示限制属性再查询一次,如果添加显示限制后只查询到一个,说明就这样就行,否则继续往上找
                var tempElement2 = this.findElementsByXpath("//" + endNode2);
                if (tempElement2.length == 1) {
                    if (tempElement2[0] == e) {
                        return "//" + endNode2;
                    } else {
                        return null;
                    }
                }
            }
        }
    } catch (error) {
        return null;
    }
    return null;
});

//自定义查询,向上找最多5层父元素,如果有label,则用label的text内容定位这个元素,如果还是有多个,在尝试往上找最多5层判断是否隐藏
LocatorBuilders.add('xpath:label:text', function(e) {
    try {
        var notcontains = '[not(contains(@style,"display: none"))]';
        var tempeleClientRect = e.getBoundingClientRect();
        var findLabel = e;
        var targetLabel = null;
        var current = e;
        var endNode = "";
        var endNode1 = "";
        var endNode2 = "";
        //先往上找5层,看看找到label,获取5层父类,然后查找子类中是否有label,没有的话直接返回
        for (var i = 0; i <= 5; i++) {
            findLabel = findLabel.parentNode;
            if (findLabel != null) {
                var hasLabel = findLabel.getElementsByTagName('label');
                //有label
                if (hasLabel.length > 0) {
                    //获取第一个label,判断label顶部边框在元素底部边框之上,label的左边框在元素右侧边框之左,其他位置的label判定为不符合要求,直接返回null
                    var tempClientRect = hasLabel[0].getBoundingClientRect();
                    if (tempeleClientRect.bottom > tempClientRect.top && tempeleClientRect.right > tempClientRect.left) {
                        //如果label有text,则就用这个label
                        var temp = this.processText(hasLabel[0].textContent);
                        if (temp != "") {
                            targetLabel = hasLabel[0];
                            break;
                        }
                    } else {
                        //第一个label不符合要求,返回
                        return null;
                    }
                }
            } else {
                //找不到父元素了,返回
                return null;
            }
        }
        //如果找到了label,继续,如果没找到,则返回
        if (targetLabel != null) {
            //先往上找父类并判断和label的相对位置
            for (var i = 0; i <= 5; i++) {
                var tempint = 0;
                //第一次不用比较位置.直接写上标签名
                if (i == 0) {
                    endNode = current.localName;
                } else {
                    current = current.parentNode;
                    //因为上面已经判断过了,所以父标签不可能是null了,就不做null判断了.直接比较位置,大于4说明当前的元素已经是label父类了,跳出循环准备判断是否是唯一的,否则继续拼接xpath
                    tempint = targetLabel.compareDocumentPosition(current);
                    if (tempint > 4) {
                        var tempNode = endNode;
                        endNode = current.localName + "/label[contains(text(),\"" + this.processText(targetLabel.textContent) + "\")]/following-sibling::" + endNode;
                        if (current.className != "") {
                            endNode1 = current.localName + "[@class=\"" + current.className + "\"]" + "/label[contains(text(),\"" + this.processText(targetLabel.textContent) + "\")]/following-sibling::" + tempNode;
                            endNode2 = current.localName + notcontains + "[@class=\"" + current.className + "\"]" + "/label[contains(text(),\"" + this.processText(targetLabel.textContent) + "\")]/following-sibling::" + tempNode;
                        } else {
                            endNode1 = current.localName + "/label[contains(text(),\"" + this.processText(targetLabel.textContent) + "\")]/following-sibling::" + tempNode;
                            endNode2 = current.localName + notcontains + "/label[contains(text(),\"" + this.processText(targetLabel.textContent) + "\")]/following-sibling::" + tempNode;
                        }
                        break;
                    } else {
                        endNode = current.localName + "/" + endNode;
                    }

                }

            }
            // 开始查询,最多再网上查询5层
            for (var i = 0; i <= 5; i++) {
                if (i == 0) {
                    //第一次循环不需要判断
                } else {
                    //后续循环往前面添加
                    current = current.parentNode;
                    if (current != null) {
                        var tempNode = endNode;
                        endNode = current.localName + "/" + endNode;
                        if (current.className != "") {
                            endNode1 = current.localName + "[@class=\"" + current.className + "\"]" + "/" + tempNode;
                            endNode2 = current.localName + notcontains + "[@class=\"" + current.className + "\"]" + "/" + tempNode;
                        } else {
                            endNode1 = current.localName + "/" + tempNode;
                            endNode2 = current.localName + notcontains + "/" + tempNode;
                        }
                    } else {
                        //没有父类了,返回
                        return null;
                    }
                }
                var tempElement = this.findElementsByXpath("//" + endNode);
                //如果不添加不显示的限制就只能查到一个,说明就用这一个就行
                if (tempElement.length == 1) {
                    if (tempElement[0] == e) {
                        return "//" + endNode;
                    } else {
                        return null;
                    }
                } else if (tempElement.length == 0) {
                    //如果查询不到说明元素没有这样的定位方式,不过应该不会出现这样的情况
                    return null;
                } else {
                    //如果查询到多个,则添加class属性再查询一次,如果添加显示限制后只查询到一个,说明就这样就行,否则继续往上找
                    var tempElement1 = this.findElementsByXpath("//" + endNode1);
                    if (tempElement1.length == 1) {
                        if (tempElement1[0] == e) {
                            return "//" + endNode1;
                        } else {
                            return null;
                        }
                    }
                    //如果查询到多个,则添加class属性和显示限制属性再查询一次,如果添加显示限制后只查询到一个,说明就这样就行,否则继续往上找
                    var tempElement2 = this.findElementsByXpath("//" + endNode2);
                    if (tempElement2.length == 1) {
                        if (tempElement2[0] == e) {
                            return "//" + endNode2;
                        } else {
                            return null;
                        }
                    }
                }
            }
        } else {
            //上面没有定位到label.返回
            return null;
        }
    } catch (error) {
        console.error(error);
        //有错误,返回
        return null;
    }
    //执行过去啥也没有返回则返回null
    return null;
});
//自定义查询,根据元素标签名和class查询,没有class或者class不是唯一的则返回null
LocatorBuilders.add('xpath:class', function(e) {
    try {
        if (e.className != "") {
            var tempElement = this.findElementsByXpath("//" + e.localName + "[@class=\"" + e.className + "\"]");
            if (tempElement.length == 1) {
                if (tempElement[0] == e) {
                    return "//" + e.localName + "[@class=\"" + e.className + "\"]";
                } else {
                    //如果不和元素一样,返回null,应该不会出现
                    return null;
                }
            } else {
                //不是唯一或没找到,不用class定位
                return null;
            }
        } else {
            //没有class,不用class定位
            return null;
        }
    } catch (error) {
        //有错误,返回
        return null;
    }
    //执行过去啥也没有返回则返回null
    return null;
});

//如果chrome.storage.local有排序,则顺序修改下
LocatorBuilders.setKatalonLocatorsPriorityStorage = function() {
    chrome.storage.local.get("katalonLocatorsPriorityStorage", function(result) {
        if ("katalonLocatorsPriorityStorage" in result) {
            if (result["katalonLocatorsPriorityStorage"] != "") {
                LocatorBuilders.setPreferredOrder(result["katalonLocatorsPriorityStorage"]);
            }
        }
    })
};
//如果chrome.storage.local有排序,则顺序修改下
LocatorBuilders.setLocatorsSort = function() {
    chrome.storage.local.get("isKatalonLocatorsSort", function(result) {
        if ("isKatalonLocatorsSort" in result) {
            if (result["isKatalonLocatorsSort"] != "") {
                LocatorBuilders.setLocatorsSort1(result["isKatalonLocatorsSort"]);
            }
        }
    })
};

/**
 设置是否使用自动判断录制优先级
 */
LocatorBuilders.setLocatorsSort1 = function(booleanStr) {
    if (typeof booleanStr === 'string') {
        if (booleanStr == "true") {
            locatorsSort = true;
        } else if (booleanStr == "false") {
            locatorsSort = false;
        } else {
            locatorsSort = false;
        }
    }
};
LocatorBuilders.setKatalonLocatorsPriorityStorage();
LocatorBuilders.setLocatorsSort();